import traceback
import json
from datetime import datetime, timedelta
from loguru import logger

from core.base_class import BaseClass
from lib.spider.lianjia_spider import LianJiaSpider
from lib.spider.wiwj_spider import WiWjSpider
from lib.database import init_database, get_db
from lib.database.community import service as community_service
from lib.database.community.models import Community
from lib.database.house import service as house_service
from lib.database.house.models import House
from lib.utils import price_by_tax


class HouseTrack(BaseClass):
    def __init__(self):
        BaseClass.__init__(self)
        self.communities = json.loads(self.config["关注的小区"])
        self.conditions = {
            "房型": [self.config["房型"]],
            "用途": [self.config["用途"]],
            "面积": self.config["面积"]
        }
        init_database(self.config["数据存储地址"])
        self.db = get_db()
        self.lianjia_spider = LianJiaSpider(self.config["城市"])
        self.wiwj_spider = WiWjSpider(self.config["城市"])

        self.web_data = {}

    def __del__(self):
        self.db.close()

    def _get_spider(self, link):
        if link.startswith(self.wiwj_spider.host):
            return self.wiwj_spider
        elif link.startswith(self.lianjia_spider.host):
            return self.lianjia_spider
        else:
            raise Exception(f"房源连接异常: {link}")

    def create_or_update_community(self, community_name: str) -> Community:
        def update_community_info(lian_jia_id):
            community_basic_info.update(self.lianjia_spider.community_basic_info1(community_id=lian_jia_id))
            community_basic_info.update(self.lianjia_spider.community_basic_info2(community_id=lian_jia_id))
            try:
                base_info = self.wiwj_spider.community_basic_info(community_name=community_name)
                if base_info is not None:
                    community_basic_info.update(base_info)
                else:
                    raise Exception("not found")
            except Exception as e:
                traceback.print_exc()
                logger.warning(f"获取小区【{community_name}】的建筑年代和地铁距离失败，请修正小区的名字。原因：{e}")
                community_basic_info.update({"birthday": "", "traffic": ""})

        community = community_service.get_by_name(db=self.db, name=community_name)
        if community is None:
            # 第一次爬取该小区信息
            community_id = self.lianjia_spider.get_community_id(name=community_name)
            community_basic_info = {"name": community_name, "lian_jia_id": community_id}
            update_community_info(lian_jia_id=community_id)
            community = community_service.create(db=self.db, community_in=community_basic_info)
            community_service.create_dyn(db=self.db, community_in=community_basic_info, community=community)

        else:
            # 更新小区的动态数据
            community_basic_info = {}
            update_community_info(lian_jia_id=community.lian_jia_id)
            # 图片可能需要更新
            community = community_service.update(db=self.db, community_in=community_basic_info, community=community)
            community_service.create_dyn(db=self.db, community_in=community_basic_info, community=community)
        return community

    def create_or_update_house(self, house_href: str, community: Community):
        spider = self._get_spider(house_href)

        # 获取房源信息
        try:
            house_info = spider.house_info(house_href)
        except Exception as e:
            traceback.print_exc()
            logger.warning(f"获取房源信息异常: {e}")
            return

        # 创建/更新 房源信息
        house = house_service.get_by_link(db=self.db, link=house_href)
        if house is None:
            house_info.update({"link": house_href, "is_sell_out": False})
            house = house_service.create(db=self.db, house_in=house_info, community=community)
            house_service.create_dyn(db=self.db, house_in=house_info, house=house)
        else:
            house_info.update({"is_sell_out": False})
            house = house_service.update(db=self.db, house_in=house_info, house=house)
            house_dyn = house_service.get_newest_dyn_by_house(db=self.db, house=house)
            if house_dyn.price != house_info["price"]:
                house_service.create_dyn(db=self.db, house_in=house_info, house=house)  # 记录变更的售价
        return house

    def update_sold_house(self, community: Community, houses: list[House]):
        all_not_sell_houses = [house for house in community.houses if not house.is_sell_out]
        ids = [house.id for house in houses if house]
        need_check_houses = filter(lambda x: x.id not in ids, all_not_sell_houses)
        for house in need_check_houses:
            spider = self._get_spider(house.link)

            try:
                house_info = spider.house_info(house.link)
            except Exception as e:
                logger.debug(f"房源【{house.link}】已出售! {e}")
                house_info = {"is_sell_out": True, "sell_out_date": datetime.today()}

            house_service.update(db=self.db, house_in=house_info, house=house)

    def start_crawling(self):
        """爬取链家数据, 并保存到数据库"""
        for community_name in self.communities:
            logger.success(f"开始爬取小区信息：{community_name}")

            # 创建/更新小区的基本信息（以链家的信息为主）
            community = self.create_or_update_community(community_name)

            # 根据查询条件，获取指定小区的房源列表
            lianjia_house_list = self.lianjia_spider.community_house_list(
                community_id=community.lian_jia_id,
                conditions=self.conditions
            )
            logger.debug(f"链家搜索到【{community.name}】房源个数为{len(lianjia_house_list)}")
            wiwj_house_list = self.wiwj_spider.community_house_list(
                community_name=community_name,
                conditions=self.conditions
            )
            logger.debug(f"我爱我家搜索到【{community_name}】房源个数为{len(wiwj_house_list)}")

            # 将房源列表交叉混合
            house_list = \
                [link for pair in zip(lianjia_house_list, wiwj_house_list) for link in pair] + \
                lianjia_house_list[len(wiwj_house_list):] + wiwj_house_list[len(lianjia_house_list):]

            # 创建/更新房源信息
            houses = [self.create_or_update_house(house_href, community) for house_href in house_list]

            # 更新已经下架(出售)或者房屋基本信息变更的房源
            self.update_sold_house(community, houses)

    @staticmethod
    def _delete_same_code_house(houses) -> list[House]:
        """如果存在相同code，则删除房价更高的那个房源"""
        lian_jia_houses = {h.code: h for h in houses if h.is_lian_jia}
        wiwj_houses = {h.code: h for h in houses if h.is_wiwj}
        ret = []
        for code in lian_jia_houses:
            if not wiwj_houses.__contains__(code):
                ret.append(lian_jia_houses[code])
            else:
                if price_by_tax(lian_jia_houses[code]) <= price_by_tax(wiwj_houses[code]):
                    ret.append(lian_jia_houses[code])
                else:
                    ret.append(wiwj_houses[code])
        return list(filter(lambda x: x.is_wiwj and not lian_jia_houses.__contains__(x.code), houses)) + ret

    def analysis_community(self, community: Community):
        """分析小区，生成小区详情"""
        def get_floor_lowest_price(floor: str):
            not_sold_houses = [house for house in community.houses if not house.is_sell_out and floor == house.floor]
            lowest = min(not_sold_houses, key=lambda x: price_by_tax(x))
            return f"{price_by_tax(lowest)}({lowest.price})"

        def get_sold_lowest_price(floor, recent_days=6*180):
            start_date = datetime.today() - timedelta(days=recent_days)
            sold_houses = house_service.get_recently_sold_houses(
                db=self.db, community=community, recent_date=start_date
            )
            houses = [house for house in sold_houses if floor == house.floor]
            if not houses:
                return '无成交'
            lowest = min(houses, key=lambda x: price_by_tax(x))
            return f"{price_by_tax(lowest)}({lowest.price})"

        def get_previous_month_see():
            previous_community_dyn = community_service.get_previous_dyn(db=self.db, community=community)
            if previous_community_dyn:
                if previous_community_dyn.month_see < community.month_see:
                    return f"📈{community.month_see-previous_community_dyn.month_see}"
                elif previous_community_dyn.month_see > community.month_see:
                    return f"📉{previous_community_dyn.month_see-community.month_see}"
            return ""

        def get_previous_sell_ratio():
            previous_community_dyn = community_service.get_previous_dyn(db=self.db, community=community)
            if previous_community_dyn:
                if previous_community_dyn.sell_count < community.sell_count:
                    return f"📈{community.sell_count-previous_community_dyn.sell_count}"
                elif previous_community_dyn.sell_count > community.sell_count:
                    return f"📉{previous_community_dyn.sell_count-community.sell_count}"
            return ""

        community_row = {
            "小区名": community.name,
            "区域": f"{community.district}-{community.area}",
            "年限": community.birthday if community.birthday else "未知",
            "交通": community.traffic,
            "出售率": f"{round(100 * community.sell_count / community.house_count, 2)}%{get_previous_sell_ratio()}",
            "低楼层最低|半年内已售最低": f'{get_floor_lowest_price("低楼层")}|{get_sold_lowest_price("低楼层")}',
            "中楼层最低|半年内已售最低": f'{get_floor_lowest_price("中楼层")}|{get_sold_lowest_price("中楼层")}',
            "高楼层最低|半年内已售最低": f'{get_floor_lowest_price("高楼层")}|{get_sold_lowest_price("高楼层")}',
            "3月成交(链家)|6月成交(我家)":
                f'{community.three_month_sell_count}套|'
                f'<a href="{self.wiwj_spider.host}/sold/{community.wiwj_id}/" target="_blank">'
                f'{community.wiwj_six_month_sell_count}套</a>',
            "30天带看量": f"{community.month_see}{get_previous_month_see()}",
        }
        if not self.web_data.__contains__("小区详情"):
            self.web_data["小区详情"] = {"headers": [i for i in community_row], "table": [community_row]}
        else:
            self.web_data["小区详情"]["table"].append(community_row)

    def analysis_low_price_house(self, community: Community, display_count: int):
        """分析低价房源"""
        def get_floor_lowest_price_houses():
            not_sold_houses = [h for h in community.houses if not h.is_sell_out and floor == h.floor]
            # 通过房源核验统一编码 剔除多余的，保留低价的
            not_sold_houses = self._delete_same_code_house(not_sold_houses)
            # 返回最低价的房子
            return sorted(not_sold_houses, key=lambda x: price_by_tax(x))[:display_count]

        def get_recent_price(recent_days=7):
            start_date = datetime.today() - timedelta(days=recent_days)
            house_dyn = house_service.get_recent_dyn_by_house(db=self.db, house=house, recent_date=start_date)
            if house_dyn:
                if house.price < house_dyn.price:
                    return f"📉{house_dyn.price-house.price}"
                else:
                    return f"📈{house.price-house_dyn.price}"
            return ""

        for floor in ["低楼层", "中楼层", "高楼层"]:
            houses = get_floor_lowest_price_houses()
            for house in houses:
                total_price = price_by_tax(house)
                low_house_row = {
                    "小区名": f'<a href="{house.link}" target="_blank">{community.name}</a>',
                    "价格": f"{total_price}({house.price}){get_recent_price()}",
                    "元/平": int(10000 * total_price / float(house.size)),
                    "楼层": house.floor,
                    "户型": house.type,
                    "装修": house.decoration,
                    "建筑面积": house.size
                }
                if not self.web_data.__contains__("低价房源"):
                    self.web_data["低价房源"] = {"headers": [i for i in low_house_row], "table": [low_house_row]}
                else:
                    self.web_data["低价房源"]["table"].append(low_house_row)

    def analysis_sold_house(self, community: Community, recent_days: int):
        start_date = datetime.today() - timedelta(days=recent_days)
        houses = house_service.get_recently_sold_houses(db=self.db, community=community, recent_date=start_date)
        # 通过房源核验统一编码 剔除多余的，保留更低价的
        houses = self._delete_same_code_house(houses)

        for house in houses:
            total_price = price_by_tax(house)
            if "5i5j.com" in house.link:
                link = house.link.replace("ershoufang", "sold")
                sold_house_row = {
                    "小区名": f'<a href="{link}" target="_blank">{community.name}<a>',
                }
            else:
                sold_house_row = {
                    "小区名": community.name,
                }

            sold_house_row.update({
                "价格": f"{total_price}({house.price})",
                "元/平": int(10000 * total_price / float(house.size)),
                "楼层": house.floor,
                "户型": house.type,
                "装修": house.decoration,
                "建筑面积": house.size,
                "出售/下架时间": house.sell_out_date.strftime("%Y-%m-%d")
            })

            title = f"近{recent_days}天已售房源"
            if not self.web_data.__contains__(title):
                self.web_data[title] = {"headers": [i for i in sold_house_row], "table": [sold_house_row]}
            else:
                self.web_data[title]["table"].append(sold_house_row)

    def analysis_special_house(self, community: Community):
        pass

    def analysis(self):
        """对数据进行分析，生成1. 特别关注房源(真实价格 < 第二最低*90%)，小区整体情况，当前最低价房源，已售房源"""
        for community_name in self.communities:
            community = community_service.get_by_name(db=self.db, name=community_name)
            if not community:
                continue
            logger.success(f"开始对房源进行分析：{community_name}")
            self.analysis_special_house(community)
            self.analysis_community(community)  # 小区详情
            self.analysis_low_price_house(community, 2)  # 低价房源详情
            self.analysis_sold_house(community, 6*30)  # 近半年已售房源


if __name__ == '__main__':
    house_track = HouseTrack()
    house_track.start_crawling()
    house_track.analysis()
    house_track.create_html()
